﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.Specialized;
using System.Xml;
using EOS.Framework.Kernel;
using Microsoft.Practices.Unity;

namespace EOS.Framework.Web.Mvc.Rendering
{
    delegate string MarkupFileValueFoundHandler(string value);
    delegate bool MarkupFileControlFoundHandler(MarkupFileControlFoundParameters parameters);
    delegate void MarkupFileRenderHandler(IXhtmlWriter writer, PageContext context, string regionName);

    class MarkupFile
    {
        private XmlDocument _xmlDocument;
        private XmlElement _xmlElement;

        #region Properties

        private MarkupFileRenderHandler _renderInner;

        public MarkupFileRenderHandler RenderInner
        {
            get { return _renderInner; }
            set { _renderInner = value; }
        }

        private MarkupFileRenderHandler _renderOuter;

        public MarkupFileRenderHandler RenderOuter
        {
            get { return _renderOuter; }
            set { _renderOuter = value; }
        }

        private MarkupFileRenderHandler _renderRegion;

        public MarkupFileRenderHandler RenderRegion
        {
            get { return _renderRegion; }
            set { _renderRegion = value; }
        }

        private MarkupFileControlFoundHandler _renderControl;

        public MarkupFileControlFoundHandler RenderControl
        {
            get { return _renderControl; }
            set { _renderControl = value; }
        }

        private MarkupFileValueFoundHandler _valueFound;

        public MarkupFileValueFoundHandler ValueFound
        {
            get { return _valueFound; }
            set { _valueFound = value; }
        }

        #endregion

        public MarkupFile(XmlDocument xmlDocument)
        {
            this._xmlDocument = xmlDocument;
        }

        #region Rendering

        private string GetId(XmlElement element)
        {
            string id = null;

            bool ignore = false;
            foreach (XmlAttribute attribute in element.Attributes)
            {
                if (attribute.Name.Equals(Constants.ControlId))
                {
                    id = attribute.Value;
                }
                if (attribute.Name.Equals(Constants.ControlIgnore) && attribute.Value.ToLower().Equals("true"))
                {
                    ignore = true;
                }
            }

            if (!ignore)
            {
                if (element.Name.ToLower().Equals(Constants.HtmlInput) ||
                    element.Name.ToLower().Equals(Constants.HtmlButton) ||
                    element.Name.ToLower().Equals(Constants.HtmlSelect) ||
                    element.Name.ToLower().Equals(Constants.HtmlTextarea))
                {
                    XmlNode attribute = element.SelectSingleNode("attribute::" + Constants.HtmlName);
                    if (attribute != null)
                    {
                        id = ((XmlAttribute)attribute).Value;
                    }
                }
            }

            return id;
        }

        public bool ReplaceRegion(IXhtmlWriter writer, PageContext context, string regionName)
        {
            bool couldReplace = false;

            foreach (XmlNode node in _xmlElement.ChildNodes)
            {
                XmlElement child = node as XmlElement;
                if (child != null && child.Name.ToLower().Equals(Constants.ReplaceTag) && child.Attributes["name"].Value == regionName)
                {
                    Traverse(child, writer, context);
                    couldReplace = true;
                    break;
                }
            }
            return couldReplace;
        }

        public void RenderMarkup(IXhtmlWriter writer, PageContext context, object token)
        {
            if (token == null)
            {
                RenderNode(writer, context, _xmlDocument.DocumentElement);
            }
            else
            {
                if (token is XmlElement == false && token is XmlText)
                {
                    throw new Exception();
                }

                XmlNode tokenAsNode = (XmlNode)token;
                switch (tokenAsNode.NodeType)
                {
                    case XmlNodeType.Element:
                        Traverse((XmlElement)tokenAsNode, writer, context);
                        break;
                }
            }

            _xmlElement = null;
        }

        private void RenderNode(IXhtmlWriter writer, PageContext context, XmlElement element)
        {
            _xmlElement = element;

            string id = GetId(element);
            if (id != null)
            {
                if (HandleControl(element, id, writer, context))
                {
                    return;
                }
            }

            if (element.Name.StartsWith(Constants.Namespace))
            {
                string name = null;
                foreach (XmlAttribute attribute in element.Attributes)
                {
                    if (attribute.Name == "name")
                        name = attribute.Value;
                }
                switch (element.Name)
                {
                    case Constants.InsertTag:
                        HandleInsert(element, name, writer, context);
                        break;
                    case Constants.RegionTag:
                        HandleRegion(element, name, writer, context);
                        break;
                }
            }
            else
            {
                writer.WriteStartElement(element.Name);
                foreach (XmlAttribute attribute in element.Attributes)
                {
                    if (attribute.Name.StartsWith("xmlns") == false &&
                        attribute.Name.StartsWith(Constants.Namespace) == false)
                    {
                        string innerText = attribute.InnerText;

                        foreach (string value in MarkupHelper.FindValues(innerText))
                        {
                            string translated = null;
                            if (ValueFound != null)
                            {
                                translated = ValueFound(value);
                            }

                            if (translated != null)
                            {
                                innerText = innerText.Replace("#{" + value + "}", translated);
                            }
                        }
                        

                        if ((element.Name.ToLower().Equals("link") && attribute.Name.ToLower().Equals("href")) ||
                            (element.Name.ToLower().Equals("img") && attribute.Name.ToLower().Equals("src")) ||
                            (element.Name.ToLower().Equals("a") && attribute.Name.ToLower().Equals("href")))
                        {
                            if (innerText.ToLower().StartsWith("ftp") == false &&
                                innerText.ToLower().StartsWith("http") == false &&
                                innerText.ToLower().StartsWith("javascript") == false &&
                                innerText.ToLower().StartsWith(context.ApplicationPath) == false)
                            {
                                innerText = UrlPath.ToAbsolutPath(innerText);
                            }
                        }

                        writer.WriteAttribute(attribute.Name, innerText);
                    }
                }
                Traverse(element, writer, context);

                writer.WriteEndElement();
            }
        }

        private void Traverse(XmlElement element, IXhtmlWriter writer, PageContext context)
        {
            foreach (XmlNode child in element.ChildNodes)
            {
                switch (child.NodeType)
                {
                    case XmlNodeType.Element:
                        RenderNode(writer, context, (XmlElement)child);
                        break;
                    case XmlNodeType.Text:
                        RenderNode(writer, context, (XmlText)child);
                        break;
                }
            }
        }

        private void RenderNode(IXhtmlWriter writer, PageContext context, XmlText text)
        {
            string output = text.Value;

            foreach (string value in MarkupHelper.FindValues(output))
            {
                string translated = null;
                if (ValueFound != null)
                {
                    translated = ValueFound(value);
                }

                if (translated != null)
                {
                    output = output.Replace("#{" + value + "}", translated);
                }
            }

            writer.WriteRaw(output);
        }

        private bool HandleControl(XmlElement element, string controlName, IXhtmlWriter writer, PageContext context)
        {
            if (RenderControl != null)
            {
                MarkupFileControlFoundParameters parameters = new MarkupFileControlFoundParameters(element, controlName, element.InnerXml, element.Name, writer, context);
                foreach (XmlAttribute attribute in element.Attributes)
                {
                    if (attribute.Name.ToLower().StartsWith("xmlns") == false &&
                        attribute.Name.ToLower().StartsWith(Constants.Namespace) == false)
                    {
                        parameters.Parameters.Add(attribute.Name, attribute.Value);
                    }
                }
                return RenderControl(parameters);
            }

            return false;
        }

        private void HandleRegion(XmlElement element, string regionName, IXhtmlWriter writer, PageContext context)
        {
            if (RenderRegion != null)
            {
                RenderRegion(writer, context, regionName);
            }
        }

        private void HandleInsert(XmlElement element, string markupName, IXhtmlWriter writer, PageContext context)
        {
            NameValueCollection parameters = new NameValueCollection();
            foreach (XmlAttribute attribute in element.Attributes)
            {
                if (attribute.Name.ToLower().StartsWith("xmlns") == false &&
                    attribute.Name.ToLower().StartsWith(Constants.Namespace) == false &&
                    attribute.Name.ToLower().Equals("name") == false)
                {
                    parameters.Add(attribute.Name, attribute.Value);
                }
            }

            bool doReplace = false;
            foreach (XmlNode node in element.ChildNodes)
            {
                XmlElement child = node as XmlElement;
                if (child != null && child.Name.ToLower().Equals(Constants.ReplaceTag))
                {
                    doReplace = true;

                }
            }

            if (doReplace)
            {
                if (RenderOuter != null)
                {
                    RenderOuter(writer, context, markupName);
                }
            }
            else
            {
                if (RenderInner != null)
                {
                    RenderInner(writer, context, markupName);
                }
            }
        }

        #endregion

        public string GetMarkupByToken(object token)
        {
            if (token == null)
            {
                return _xmlDocument.DocumentElement.InnerXml;
            }
            else
            {
                if (token is XmlElement == false && token is XmlText)
                {
                    throw new Exception();
                }
                else
                {
                    return ((XmlNode)token).InnerXml;
                }
            }
        }
    }
}
